import numpy as np
from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton,
                             QStackedWidget, QHBoxLayout, QLabel,QAction,QTextEdit,
                             QDialog, QDesktopWidget,QMessageBox,QFrame, QSpacerItem,
                             QSizePolicy, QInputDialog)
from PyQt5.QtGui import QIcon,QFont,QDesktopServices,QPixmap
from PyQt5.QtCore import QSize,QUrl,Qt
from sklearn.model_selection import train_test_split
from datetime import datetime
import pyqtgraph as pg
import json
import csv
import os
from MySerial import  MySerial
from FetchDataWindow import  FetchDataWindow
from PredictWindow import PredictWindow
from PortSelectDialog import SerialPortSelectionDialog
from EEGProcessor import EEGProcessor
from SVMModel import SVMModel
from tool.grid import find_parameters

VERSION = "V1.0.1"    # 当前版本号

APP_Height = 1200    # 应用的大小（高）
APP_Width = 1600   # 应用的大小（宽）

Label_Height = 200  # 标签的大小（高）
Label_Width = 250   #标签的大小（宽）
Button_Height = 80  #按钮的大小（高）
Button_Width = 100  #按钮的大小（宽）

MAX_SHOWWAVE = 10     # 波形显示界面 最大数据组数（n*(512+8+3)）
MAX_FEATHDATA = 10    # 数据提取界面 最大数据组数（n*(512+8+3)）
MAX_PREDICT = 5       # 模型预测界面 最大数据组数（n*(512+8+3)）

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # 设置窗口标题和大小
        # tray_icon = QSystemTrayIcon(self)
        self.setWindowTitle("IRS")                      # IntentionRecognition
        self.setWindowIcon(QIcon("./images/logo.ico"))  # 左上角图标

        self.center()                   # 居中显示
        self.ShowWave_Rawdata = []      # 波形显示界面的原始数据  最多存储MAX_SHOWWAVE帧
        self.ShowWave_Signal = []       # 波形显示界面的信号强度
        self.ShowWave_Attention = []    # 波形显示界面的专注度
        self.ShowWave_Meditation = []   # 波形显示界面的放松度
        self.Train_NormalData = []      # 训练界面的原始数据  最多存储MAX_FEATHDATA帧
        self.Train_BasicData = []       # 训练界面的基准数据
        self.Predict_NormalData = []    # 预测界面的原始数据
        self.Predict_BasicData = []     # 预测界面的基准数据
        self.currentButton = 1          # 当前按下的是哪个按钮  4:详细信息 3:模型预测 2:模型训练 1:波形显示
        self.selected_port = None       # 端口
        self.old_port = None            # 上次选择的端口
        self.data_Type = -1             # 当前传输的数据类型  0:正常数据  1:基准数据

        self.file_rawData = r".\data\rawData.csv"  # 保存EEG数据文件
        self.file_model = r".\model\model"         # 模型文件
        self.file_json = r".\config\model.json"    # 模型配置文件
        self.file_svmData = r".\data\svmData"          # 符合libsvm格式的数据文件

        self.serial = MySerial()
        self.serial.data_received_signal.connect(self.Callback_BigMsg)       # 每接受到一个大包，就调用Callback_BigMsg


        self.layout_mainInit()          # 主布局初始化
        self.page_showWaveInit()        # 页面布局初始化
        self.page_moduleTrainInit()     # 页面布局初始化
        self.page_modulePredictInit()   # 页面布局初始化
        self.page_LogInit()             # 页面布局初始化
        # self.page_informationInit()     # 页面布局初始化
        self.create_menu()              # 创建菜单栏
        text = "==========APP Init=========="
        self.INFO(text)
        # self.ClearCSV()

    # 页面居中
    def center(self):
        screen = QDesktopWidget().screenGeometry()  # 获取屏幕的大小
        self.setGeometry((screen.width()-APP_Width)//2, (screen.height()-APP_Height)//2, APP_Width, APP_Height)

    # 清除文件数据
    def ClearCSV(self):
        with open(self.file_rawData, "w", newline='') as csvfile:
            pass
        with open(self.file_svmData, "w", newline='') as csvfile:
            pass
        text = "清理数据文件"
        self.INFO(text)
        QMessageBox.information(self, "清除数据", "数据已清除")

    # 写数据到文件中
    def WriteToCSV(self,label,data):
        with open(self.file_rawData, "a", newline='') as csvfile:
            writer = csv.writer(csvfile)
            for rawData in data:
                dataToWrite = [label] + rawData
                writer.writerow(dataToWrite)  # 写入到 CSV 文件

    # 从文件中读数据
    def ReadFromCSV(self):
        label = []  # 标签
        property = []  # 原始数据
        with open(self.file_rawData, mode='r', newline='', encoding='utf-8') as file:
            reader = csv.reader(file)
            for row in reader:
                label.append(int(row[0]))
                property.append([float(x) for x in row[1:]])
        return label, property

    # 读取json配置文件
    def ReadFromJSON(self):
        with open(self.file_json,mode='r', encoding='utf-8') as file:
            config = json.load(file)
        return config

    # 向json文件写数据
    def WriteToJSON(self,config):
        with open(self.file_json, "w", encoding='utf-8') as file:
            json.dump(config, file, indent=4)

    # 格式转换并写入文件
    def FormatChangeAndWrite(self, label, scale):
        j = 0
        with open(self.file_svmData, mode='w', newline='', encoding='utf-8') as file:
            for _data in scale:
                values_str = [f"{i + 1}:{v}" for i, v in enumerate(_data)]
                values_str = [label[j]] + values_str
                write_data = [str(item) for item in values_str]
                file.write(" ".join(write_data) + "\n")
                j += 1

    def page_showWaveInit(self):        #波形显示 页面初始化
        Layout_Graph = QVBoxLayout(self.widget_showWave)                    # 为图表创建布局
        graphics = pg.GraphicsLayoutWidget()
        Layout_Graph.addWidget(graphics)
        Graph_EEGWave = graphics.addPlot(row=0, col=0, title="EEG WAVE")    # 创建图表
        Graph_EEGWave.addLegend()
        Graph_EEGWave.setMouseEnabled(y=False)  # 禁用轴操作
        self.Curve_RawWave = Graph_EEGWave.plot([], [], pen=pg.mkPen(color=(144, 238, 144), width=3),name="RawWave")  # 创建曲线(原始数据波形)
        self.Signal = Graph_EEGWave.plot([], [], pen=pg.mkPen(color=(255, 0, 0), width=3),name="Signal")  # 创建曲线(信号强度)

    def page_moduleTrainInit(self):     #模型训练 页面初始化
        Layout_moduleTrain = QVBoxLayout(self.widget_moduleTrain)  # 模型训练 主布局
        frame_fetchData = QFrame()
        frame_fetchData.setStyleSheet(
                                    "QFrame {"
                                    "    background-color: 	#F0FFFF;"                    # 背景颜色
                                    "    border: 3px solid #c0c0c0;"                    # 边框粗细+颜色
                                    "    border-radius: 10px;"                          # 边框圆角半径
                                    "}"
                                )
        label_fetchData = QLabel()
        label_fetchData.setText("\n".join(list("数据提取")))
        label_fetchData.setStyleSheet("background-color:#E6E6FA;")
        label_fetchData.setFixedWidth(50)
        label_fetchData.setFixedHeight(200)
        label_fetchData.setFont(QFont('Arial', 20))
        label_fetchData.setAlignment(Qt.AlignCenter)

        button_fetchData = QPushButton("开始提取")  # 提取数据 按钮
        button_fetchData.setFixedSize(160,70)      # 按钮大小
        button_fetchData.setStyleSheet("QPushButton"
                                       "{"
                                            "background-color: 	#87CEEB;"       # 按钮背景颜色
                                            "border:2px groove gray;"            
                                            "border-radius:10px;"
                                            "padding:2px 4px;"
                                            "border-style: outset;"
                                       "}"
                                        "QPushButton:hover{background-color:rgb(229, 241, 251); color: black;}"         # 鼠标悬停
                                        "QPushButton:pressed{background-color:rgb(204, 228, 247);border-style: inset;}" # 鼠标按下
                                       )

        spacer_fetchData = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)  # 弹簧

        Layout_fetchData = QHBoxLayout(frame_fetchData)  # 提取数据  布局
        Layout_fetchData.addWidget(label_fetchData)  # 提取数据布局 增加标签组件
        Layout_fetchData.addSpacerItem(spacer_fetchData)# 提取数据布局 增加弹簧组件
        Layout_fetchData.addWidget(button_fetchData)  # 提取数据布局 增加按钮组件
        # Layout_fetchData.addSpacerItem(spacer_fetchData)  # 提取数据布局 增加弹簧组件
        Layout_moduleTrain.addWidget(frame_fetchData)  # 模型训练主布局 增加frame组件

        frame_train = QFrame()
        frame_train.setStyleSheet(
                                    "QFrame {"
                                    "    background-color: #FAF0E6;"                    # 背景颜色
                                    "    border: 3px solid #c0c0c0;"                    # 边框粗细+颜色
                                    "    border-radius: 10px;"                          # 边框圆角半径
                                    "}"
                                )
        label_train = QLabel()
        label_train.setText("\n".join(list("模型训练")))
        label_train.setStyleSheet("background-color: #FFE4C4;")
        label_train.setFixedWidth(50)
        label_train.setFixedHeight(200)
        label_train.setFont(QFont('Arial', 20))
        label_train.setAlignment(Qt.AlignCenter)

        button_train = QPushButton("开始训练")  # 开始训练 按钮
        button_train.setFixedSize(160,70)      # 按钮大小
        button_train.setStyleSheet(("QPushButton"
                                       "{"
                                            "background-color: 	#87CEEB;"       # 按钮背景颜色
                                            "border:2px groove gray;"            
                                            "border-radius:10px;"
                                            "padding:2px 4px;"
                                            "border-style: outset;"
                                       "}"
                                        "QPushButton:hover{background-color:rgb(229, 241, 251); color: black;}"         # 鼠标悬停
                                        "QPushButton:pressed{background-color:rgb(204, 228, 247);border-style: inset;}" # 鼠标按下
                                       ))
        spacer_train = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)  # 弹簧
        Layout_fetchData = QHBoxLayout(frame_train)                                     # 开始训练  布局
        Layout_fetchData.addWidget(label_train)                                         # 开始训练 增加标签组件
        Layout_fetchData.addSpacerItem(spacer_train)                                    # 开始训练 增加弹簧组件
        Layout_fetchData.addWidget(button_train)                                        # 开始训练 增加按钮组件

        Layout_moduleTrain.addWidget(frame_train)                                       # 开始训练 增加frame组件

        button_fetchData.clicked.connect(self.Callback_Button_fetchData)
        button_train.clicked.connect(self.Callback_Button_train)

    def page_modulePredictInit(self):                                                   #模型预测 页面初始化
        Layout_modulePredict = QVBoxLayout(self.widget_modulePredict)                   # 模型预测 主布局

        frame_predict = QFrame()
        frame_predict.setStyleSheet(
            "QFrame {"
            "    background-color: #E6E6FA;"  # 背景颜色
            "    border: 3px solid #c0c0c0;"  # 边框粗细+颜色
            "    border-radius: 10px;"  # 边框圆角半径
            "}"
        )
        button_predict = QPushButton("开始预测")       # 开始预测 按钮
        button_predict.setFixedSize(160, 70)          # 按钮大小
        button_predict.setStyleSheet(("QPushButton"
                                    "{"
                                    "background-color: 	#87CEEB;"  # 按钮背景颜色
                                    "border:2px groove gray;"
                                    "border-radius:10px;"
                                    "padding:2px 4px;"
                                    "border-style: outset;"
                                    "}"
                                    "QPushButton:hover{background-color:rgb(229, 241, 251); color: black;}"  # 鼠标悬停
                                    "QPushButton:pressed{background-color:rgb(204, 228, 247);border-style: inset;}"
                                    # 鼠标按下
                                    ))
        Layout_frame = QHBoxLayout(frame_predict)
        Layout_frame.addWidget(button_predict)      # 开始预测 增加按钮组件
        Layout_modulePredict.addWidget(frame_predict)  # 开始预测 增加frame组件
        button_predict.clicked.connect(self.Callback_Button_predict)

    def page_LogInit(self):                          # 日志显示 页面初始化
        Layout_Log = QVBoxLayout(self.widget_log)    # 创建日志布局
        self.Label_log = QLabel("日志")              # 创建日志标签
        self.Label_log.setStyleSheet(("QLabel"
                                    "{"
                                    "font-size:30px;"
                                    "font-weight:bold;"
                                    "}"
                                    ""))
        self.TextEdit_Log = QTextEdit()              # 创建终端
        self.TextEdit_Log.setReadOnly(True)
        self.TextEdit_Log.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        Layout_Log.addWidget(self.Label_log)
        Layout_Log.addWidget(self.TextEdit_Log)
        Layout_Log.setStretch(0, 0)
        Layout_Log.setStretch(1, 9)

    # def page_informationInit(self):     #详细信息 页面初始化
    #     pass

    def page_showWave(self):        #切换到波形显示 页面
        self.currentButton = 1
        self.serial.resume()
        self.stackedwidget_main.setCurrentWidget(self.widget_showWave)

    def page_moduleTrain(self):     #切换到模型训练 页面
        self.currentButton = 2
        self.serial.pause()
        self.stackedwidget_main.setCurrentWidget(self.widget_moduleTrain)

    def page_modulePredict(self):   #切换到模型预测 页面
        self.currentButton = 3
        self.serial.pause()
        self.stackedwidget_main.setCurrentWidget(self.widget_modulePredict)

    def page_log(self):             #切换到日志 页面
        self.currentButton = 4
        self.serial.pause()
        self.stackedwidget_main.setCurrentWidget(self.widget_log)

    # def page_information(self):     #切换到详细信息 页面
    #     self.currentButton = 5
    #     self.serial.pause()
    #     self.stackedwidget_main.setCurrentWidget(self.widget_information)


    def Callback_BigMsg(self):
        if self.currentButton == 1:                             # 波形显示
            if len(self.serial.OneFrameMsg) == 523:             # 去除掉第一个和最后一个包(不完整)  523 = 512+8+3
                self.ShowWave_Rawdata.extend(self.serial.OneFrameMsg[0:512])    # 只要前512个数据，最后三个数据舍去
                self.ShowWave_Signal.extend([self.serial.OneFrameMsg[-3]] * 512)
                # arr = np.array(self.serial.OneFrameMsg[0:512])
                # if np.sum(arr>=250) >= 10:
                #     print("眨眼")
                self.serial.OneFrameMsg.clear()
                if len(self.ShowWave_Rawdata) >= MAX_SHOWWAVE*512:      # 长度大于最大值
                    self.ShowWave_Rawdata = self.ShowWave_Rawdata[512:] # 截取后边的数据
                if len(self.ShowWave_Signal) >= MAX_SHOWWAVE * 512:
                    self.ShowWave_Signal = self.ShowWave_Signal[512:]
                self.Curve_RawWave.setData([], y=self.ShowWave_Rawdata)
                self.Signal.setData([], y=self.ShowWave_Signal)
            else:
                self.serial.OneFrameMsg.clear()
        elif self.currentButton == 2:                           # 模型训练
            if len(self.serial.OneFrameMsg) == 523 and self.serial.OneFrameMsg[-3] < 10:# 去除掉第一个和最后一个包(不完整)  523 = 512+8+3
                arr = np.array(self.serial.OneFrameMsg[0:512])
                if np.sum(arr>=250) >= 5 or np.sum(arr<=-150) >= 5:              # 眨眼信号
                    print("眨眼")
                    self.serial.OneFrameMsg.clear()
                else:
                    if self.data_Type == 0:  # 正常数据
                        self.Train_NormalData.append(self.serial.OneFrameMsg[0:512])  # 只要前512个数据，最后三个数据舍去
                        self.serial.OneFrameMsg.clear()
                        if len(self.Train_NormalData) >= MAX_FEATHDATA:  # 长度大于最大值 既一个状态的数据采集完成
                            self.serial.pause()  # 暂停串口接收
                            self.fetchdatawindow.StopFetchData()  # 停止采集
                    elif self.data_Type == 1:  # 基准数据
                        self.Train_BasicData.append(self.serial.OneFrameMsg[0:512])  # 只要前512个数据，最后三个数据舍去
                        self.serial.OneFrameMsg.clear()
            else:
                self.serial.OneFrameMsg.clear()
        elif self.currentButton == 3:                           # 模型预测
            if len(self.serial.OneFrameMsg) == 523 and self.serial.OneFrameMsg[-3] < 10:# 去除掉第一个和最后一个包(不完整)  523 = 512+8+3
                arr = np.array(self.serial.OneFrameMsg[0:512])
                if np.sum(arr>=250) >= 5 or np.sum(arr<=-150) >= 5:              # 眨眼信号
                    print("眨眼")
                    self.serial.OneFrameMsg.clear()
                else:
                    if self.data_Type == 0:                         # 正常数据
                        self.Predict_NormalData.append(self.serial.OneFrameMsg[0:512])  # 只要前512个数据，最后三个数据舍去
                        self.serial.OneFrameMsg.clear()
                        if len(self.Predict_NormalData) >= MAX_PREDICT:  # 长度大于最大值 既一个状态的数据采集完成
                            self.serial.pause()                     # 暂停串口接收
                            byte = 0x00
                            self.CallbackPredict_Signal_predict(byte)   # 对采集到的数据进行预测
                    elif self.data_Type == 1:                       # 基准数据
                        self.Predict_BasicData.append(self.serial.OneFrameMsg[0:512])  # 只要前512个数据，最后三个数据舍去
                        self.serial.OneFrameMsg.clear()
            else:
                self.serial.OneFrameMsg.clear()
        elif self.currentButton == 4:                           # 日志
            pass
        # elif self.currentButton == 5:                           # 详细信息
        #     pass

    def Callback_Button_fetchData(self):            # 提取数据按钮回调函数
        if self.selected_port == None:             # 没有选择串口
            self.ERROR("ERROR:没有选择串口!")
            QMessageBox.critical(self, '错误', '没有选择串口!')
        else:
            text = "==========数据采集=========="
            self.INFO(text)
            self.fetchdatawindow = FetchDataWindow()# 实例化采集数据类
            self.fetchdatawindow.fetchDataSignal.connect(self.CallbackShowWave_Signal_fetchData)
            self.fetchdatawindow.show()

    def Callback_Button_train(self):                # 模型训练按钮回调函数
        if os.path.getsize(self.file_rawData) == 0:     # 数据为空
            self.ERROR("ERROR:数据为空!")
            QMessageBox.critical(self, '错误', '数据为空!')
        else:
            text = "==========模型训练=========="
            self.INFO(text)
            property_raw = []                       # 特征数据(没有经过归一化的特征值)
            property_scale = []                     # 特征数据(经过归一化的特征值)
            baseLine_data = []                         # 基线数据
            normal_label = []                       # 正常数据标签
            normal_data = []                        # 正常数据

            eegProcessor = EEGProcessor()           # 实例化数据处理类
            label,rawData = self.ReadFromCSV()      # 读取原始数据以及对应的标签
            for _label,_data in zip(label,rawData): # 从原始数据中提取出基线数据和正常数据
                if _label == -1:
                    baseLine_data = _data
                else:
                    normal_label.append(_label)
                    normal_data.append(_data)
            # baselineMean = eegProcessor.GetBaseLineMean(baseLine_data)  # 获取基线的均值
            for data in normal_data:                # 遍历正常数据
                eegProcessor.loadData(data,baseLine_data)         # 加载数据
                eegProcessor.BaselineCorrection()   # 基线矫正
                _property = eegProcessor.GetProperty()# 获取特征数据（时域，频域，时频域）
                property_raw.append(_property)
                eegProcessor.clearData()            # 清除数据
            property_scale,feat_min,feat_max = eegProcessor.scale_for_train(property_raw,(-1,1))    # 特征值归一化并返回处理后的数据
            x_train, x_test, y_train, y_test = train_test_split(property_scale, normal_label, test_size=0.2, random_state=42)
            config = {"Baseline":baseLine_data,"Property_MAX":feat_max,"Property_MIN":feat_min}
            self.WriteToJSON(config)
            self.FormatChangeAndWrite(normal_label,property_scale)
            best_rate, best_param = find_parameters(self.file_svmData)
            # print(best_rate, best_param)
            property_raw.clear()
            property_scale.clear()
            svm_train = SVMModel(kernel="rbf", C=best_param["c"], gamma=best_param["g"])
            svm_train.fit(x_train,y_train)
            p_label, p_acc, p_val = svm_train.predict(x_test,y_test)
            print("训练完成，准确率:{}".format(p_acc[0]))
            text = f"训练完成，准确率:{p_acc[0]}%"
            self.INFO(text)
            QMessageBox.information(self,"训练完成",f"准确率:{p_acc[0]}%")
            svm_train.saveModel(self.file_model)

    def Callback_Button_predict(self):              # 模型预测按钮回调函数
        if self.selected_port == None:            # 没有选择串口
            self.ERROR("ERROR:没有选择串口!")
            QMessageBox.critical(self, '错误', '没有选择串口!')
        elif os.path.getsize(self.file_model) == 0:  # 模型为空
            self.ERROR("ERROR:模型为空!")
            QMessageBox.critical(self, '错误', '模型为空!')
        else:
            print("开始预测")
            text = "==========模型预测=========="
            self.INFO(text)
            self.Predict_BasicData.clear()          # 每次预测时先清空基准数据
            self.predictwindow = PredictWindow()
            self.predictwindow.predictSignal.connect(self.CallbackPredict_Signal_predict)
            self.predictwindow.show()

    def CallbackShowWave_Signal_fetchData(self,data):# 数据采集界面  回调函数
        label_index = ["上","右","下","左"]
        type = data & 0xC0                          # 获取高两位
        index = data & 0x3F                         # 获取低6位
        # 00_   结束采集正常数据  0x00
        # 01_   结束采集基准数据  0x40
        # 10_   开始采集正常数据  0x80
        # 11_   开始采集基准数据  0xc0
        if type == 0xC0:                            # 开始采集基准数据
            self.data_Type = 1
            self.serial.resume()
        elif type == 0x80:                          # 开始采集正常数据
            self.data_Type = 0
            self.serial.resume()
        elif type == 0x40:                          # 结束采集基准数据
            self.serial.pause()                     # 暂停串口接收
            print(f"基准数据长度:{len(self.Train_BasicData)}")
            _Train_BasicData = [self.Train_BasicData[-1]]
            self.WriteToCSV(-1, _Train_BasicData)# 将基准数据保存在文件中 标签 为 -1
            self.Train_BasicData.clear()
        else:                                       # 结束采集正常数据(此时已经将串口接收线程挂起了)
            print("采集完成\r\n标签：{}".format(index))
            print(f"正常数据长度:{len(self.Train_NormalData)}")
            self.WriteToCSV(index, self.Train_NormalData)  # 将正常数据保存在文件中
            text = f"标签:{label_index[index - 1]}"
            self.INFO(text)
            self.Train_NormalData.clear()

    def CallbackPredict_Signal_predict(self, data):  # 模型预测界面  回调函数
        label_index = ["上", "右", "下", "左"]
        type = data & 0xC0  # 获取高两位
        index = data & 0x3F  # 获取低6位
        # 00_   结束采集正常数据  0x00
        # 01_   结束采集基准数据  0x40
        # 10_   开始采集正常数据  0x80
        # 11_   开始采集基准数据  0xc0
        if type == 0xC0:                            # 开始采集基准数据
            self.data_Type = 1
            self.serial.resume()
        elif type == 0x80:                          # 开始采集正常数据
            self.data_Type = 0
            self.serial.resume()
        elif type == 0x40:                          # 结束采集基准数据
            self.serial.pause()                     # 暂停串口接收
            print(f"基准数据长度:{len(self.Predict_BasicData)}")
        else:                                       # 结束采集正常数据(此时已经将串口接收线程挂起了)
            print(f"正常数据长度:{len(self.Predict_NormalData)}")
            property_raw = []                       # 特征数据(没有经过归一化的特征值)
            property_scale = []                     # 特征数据(经过归一化的特征值)
            config = self.ReadFromJSON()
            svm_predict = SVMModel()
            eegProcessor = EEGProcessor()           # 实例化数据处理类
            svm_predict.loadModel(self.file_model)
            for rawdata in self.Predict_NormalData:
                eegProcessor.loadData(rawdata,config["Baseline"])      # 加载数据
                eegProcessor.BaselineCorrection()                      # 基线校正
                _property = eegProcessor.GetProperty()# 获取特征数据（时域，频域，时频域）
                property_raw.append(_property)
                eegProcessor.clearData()            # 清除数据
            property_scale = eegProcessor.scale_for_predict(property_raw,config["Property_MIN"],config["Property_MAX"],(-1,1))  # 特征值归一化并返回处理后的数据
            p_labels,p_acc,p_vals = svm_predict.predict(property_scale)
            property_raw.clear()
            property_scale.clear()
            print(p_labels)
            label = max(p_labels,key = p_labels.count)# 选择预测结果出现次数最多的标签
            label = int(label)
            text = f"预测结果:{label}:{label_index[label - 1]}"
            self.INFO(text)
            print(f"预测结果:{label}")
            self.predictwindow.ShowLabel(label)
            self.Predict_NormalData.clear()

    def Callback_tools_predict(self):
        label_index = ["上", "右", "下", "左"]
        rawData = []
        text, ok = QInputDialog.getText(self, 'predict', '输入原始数据')
        if ok and text:
            rawData = [int(x.strip()) for x in text.split(',')]
            # print(rawData)
            config = self.ReadFromJSON()
            svm_predict = SVMModel()
            eegProcessor = EEGProcessor()  # 实例化数据处理类
            svm_predict.loadModel(self.file_model)
            eegProcessor.loadData(rawData)  # 加载数据
            eegProcessor.BaselineCorrection(config["BaselineMean"])  # 基线校正
            property = eegProcessor.GetProperty()  # 获取特征数据（时域，频域，时频域）
            property_scale = eegProcessor.scale_for_predict(property, config["Property_MIN"], config["Property_MAX"],(-1, 1))  # 特征值归一化并返回处理后的数据
            p_labels, p_acc, p_vals = svm_predict.predict([property_scale])
            label = int(p_labels[0])
            QMessageBox.information(self, "predict", f"预测结果:{label_index[label-1]}")

    def create_menu(self):
        menu_bar = self.menuBar()

        # 创建系统菜单
        system_menu = menu_bar.addMenu("System")
        setup_action = QAction("Setup", self)
        setup_action.triggered.connect(self.show_serial_port_dialog)
        system_menu.addAction(setup_action)

        cleanFile_action = QAction("CleanFile", self)
        cleanFile_action.triggered.connect(self.ClearCSV)
        system_menu.addAction(cleanFile_action)

        exit_action = QAction("Exit", self)
        exit_action.triggered.connect(self.close)
        system_menu.addAction(exit_action)

        # 创建帮助菜单
        help_menu = menu_bar.addMenu("Help")
        about_action = QAction("About", self)
        about_action.triggered.connect(self.open_about_page)
        help_menu.addAction(about_action)

        predict_action = QAction("Predict", self)
        predict_action.triggered.connect(self.Callback_tools_predict)
        help_menu.addAction(predict_action)

    # 选择串口弹窗
    def show_serial_port_dialog(self):
        dialog = SerialPortSelectionDialog()
        if dialog.exec_() == QDialog.Accepted:
            self.serial.pause()                              # 暂停串口线程
            self.selected_port = dialog.get_selected_port()  # 当前选择的端口
            if self.selected_port == self.old_port:          # 这次选择的和上次选择的一样，不再次打开
                print("选择的串口没有变化")
            else:                                            # 这次选择的和上次选择的不一样，需要重新打开
                print("串口发生改变")
                if self.old_port != None:
                    self.serial.close()                      # 关闭上次的端口
                self.old_port = self.selected_port
                if self.serial.open(self.selected_port):         # 打开新的端口 如果打开成功则开始读取数据
                    self.serial.resume()
                    self.serial.start()
                else:                                            # 打开失败
                    QMessageBox.critical(self, "错误", "串口打开失败,请重试")
        else:
            print("没有选择串口")

    def open_about_page(self):
        # 跳转到网页
        QDesktopServices.openUrl(QUrl("https://blog.zixie.top"))

    def INFO(self,message):
        text = '<font color=#84bb4c>[INFO] </font>'
        text += f'<font color=black>{message}</font>'
        self.TextEdit_Log.append(self.get_current_time() + " " + text)
    def WARNING(self,message):
        text = '<font color=orange>[WARNING] </font>'
        text += f'<font color=black>{message}</font>'
        self.TextEdit_Log.append(self.get_current_time() + " " + text)
    def ERROR(self,message):
        text = '<font color=red>[ERROR] </font>'
        text += f'<font color=black>{message}</font>'
        self.TextEdit_Log.append(self.get_current_time() + " " + text)

    def get_current_time(self):
        return datetime.now().strftime("[%H:%M:%S]")

    # 主布局初始化
    def layout_mainInit(self):          # 初始化布局(主布局)
        # 创建主窗口的中心部件
        widget_central = QWidget(self)
        self.setCentralWidget(widget_central)

        # 创建一个水平布局，用于放置左侧的按钮和右侧的页面显示区域  主布局
        layout_main = QHBoxLayout(widget_central)

        # 创建一个 QStackedWidget，用于存放不同的页面
        self.stackedwidget_main = QStackedWidget()      #主堆叠布局

        # 创建多个页面（不同的 QWidget）
        self.widget_showWave = QWidget()
        self.widget_moduleTrain = QWidget()
        self.widget_modulePredict = QWidget()
        # self.widget_information = QWidget()
        self.widget_log = QWidget()

        # 将页面添加到 QStackedWidget 中
        self.stackedwidget_main.addWidget(self.widget_showWave)
        self.stackedwidget_main.addWidget(self.widget_moduleTrain)
        self.stackedwidget_main.addWidget(self.widget_modulePredict)
        # self.stackedwidget_main.addWidget(self.widget_information)
        self.stackedwidget_main.addWidget(self.widget_log)

        # 创建左侧的按钮区域
        layout_button = QVBoxLayout()           # 按钮布局

        # 创建四个按钮，分别对应不同的页面
        button_showWave = QPushButton(self)
        button_showWave.setIcon(QIcon("./images/wave.png"))
        button_showWave.setIconSize(QSize(64, 64))
        button_showWave.setToolTip("波形显示")
        button_showWave.setStyleSheet(
            "QPushButton {"
            "   border: none;"  # 默认边框透明
            "}"
            "QPushButton:hover {"
            # "   border: 10px solid;"  # 鼠标悬停时显示黑色边框
            "   background-color: rgb(130,130,130);"  # 鼠标悬停时背景颜色变化
            "}")
        button_showWave.clicked.connect(self.page_showWave)

        button_moduleTrain = QPushButton(self)
        button_moduleTrain.setIcon(QIcon("./images/train.png"))
        button_moduleTrain.setIconSize(QSize(64, 64))
        button_moduleTrain.setToolTip("模型训练")
        button_moduleTrain.setStyleSheet(
            "QPushButton {"
            "   border: none;"  # 默认边框透明
            "}"
            "QPushButton:hover {"
            # "   border: 10px solid;"  # 鼠标悬停时显示黑色边框
            "   background-color: rgb(130,130,130);"  # 鼠标悬停时背景颜色变化
            "}")
        button_moduleTrain.clicked.connect(self.page_moduleTrain)

        button_modulePredict = QPushButton(self)
        button_modulePredict.setIcon(QIcon("./images/predict.png"))
        button_modulePredict.setIconSize(QSize(64, 64))
        button_modulePredict.setToolTip("模型预测")
        button_modulePredict.setStyleSheet((
            "QPushButton {"
            "   border: none;"  # 默认边框透明
            "}"
            "QPushButton:hover {"
            # "   border: 10px solid;"  # 鼠标悬停时显示黑色边框
            "   background-color: rgb(130,130,130);"  # 鼠标悬停时背景颜色变化
            "}"))
        button_modulePredict.clicked.connect(self.page_modulePredict)

        button_log = QPushButton(self)
        button_log.setIcon(QIcon("./images/log.png"))
        button_log.setIconSize(QSize(64, 64))
        button_log.setToolTip("日志")
        button_log.setStyleSheet((
            "QPushButton {"
            "   border: none;"  # 默认边框透明
            "}"
            "QPushButton:hover {"
            # "   border: 10px solid;"  # 鼠标悬停时显示黑色边框
            "   background-color: rgb(130,130,130);"  # 鼠标悬停时背景颜色变化
            "}"))
        button_log.clicked.connect(self.page_log)

        # button_information = QPushButton(self)
        # button_information.setIcon(QIcon("./images/information.png"))
        # button_information.setIconSize(QSize(64, 64))
        # button_information.setToolTip("关于")
        # button_information.setStyleSheet((
        #     "QPushButton {"
        #     "   border: none;"  # 默认边框透明
        #     "}"
        #     "QPushButton:hover {"
        #     # "   border: 10px solid;"  # 鼠标悬停时显示黑色边框
        #     "   background-color: rgb(130,130,130);"  # 鼠标悬停时背景颜色变化
        #     "}"))
        # button_information.clicked.connect(self.page_information)

        # 将按钮添加到布局中
        layout_button.addWidget(button_showWave)
        layout_button.addWidget(button_moduleTrain)
        layout_button.addWidget(button_modulePredict)
        layout_button.addWidget(button_log)
        # layout_button.addWidget(button_information)

        # 将按钮垂直布局容器放入一个 QWidget，并设置背景颜色
        widget_button = QWidget()
        widget_button.setLayout(layout_button)
        widget_button.setStyleSheet("background-color: #273441;")  # 设置垂直布局背景颜色

        # 将按钮区域和页面区域加入主布局
        layout_main.addWidget(widget_button)
        layout_main.addWidget(self.stackedwidget_main)

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec_()
